﻿/// <reference path="linq.js"/>
/// <reference path="scripts/jquery-1.4.4.js"/>

/*
    
*/

(function (win, linq, $, undef) {
    /// <param name="$" type="jQuery"/>

    var propertyKey = Math.abs(new Date().getTime() * Math.random());
    var modelProperty = '__model' + propertyKey;
    var metadataProperty = '__metadata' + propertyKey;
    var managerMethod = '__method' + propertyKey;
    var bindingProviders = {};
    var deserializers = {};
    var serializers = {};
    var defaults = {};
    var ignoredValue = {};
    var bindingArgNames = 'metadata, model, action, parent';
    var templateIds = {};

    function each(value, callback) {
        if (value != null) {
            return $.each(value, function (n) {
                if (n == modelProperty || n == metadataProperty || n == managerMethod) return;
                return callback.apply(this, arguments);
            });
        }
        return undef;
    }

    function getMetadata() {
        var lastValue = undef;
        for (var i = 0; i < arguments.length; i++) {
            lastValue = arguments[i];
            if (lastValue != null && lastValue[metadataProperty] == true) return lastValue;
        }
        return {
            invalid: true,
            value: lastValue
        };
    }

    function metadataValueOf() {
        return this.value;
    }

    function createModelMetadata(model, property) {
        if (model[metadataProperty] == true) return model;
        var obj = {};
        obj[metadataProperty] = true;
        if ($.isArray(model)) {
            obj.length = model.length;
        }
        else if ($.isPlainObject(model)) {
            each(model, function (name, value) {
                var prop;
                if (typeof value == 'function') {
                    prop = function () {
                        return value.apply(model, arguments);
                    };
                    prop.late = function () {
                        return function () {
                            return value.apply(model, arguments);
                        };
                    };
                    obj[name] = prop;
                }
                else if ($.isArray(value)) {
                    obj[name] = prop = {
                        length: value.length
                    };
                }
                else {
                    obj[name] = prop = {};
                }
                prop[metadataProperty] = true;
                prop.changed = property && (property === '*' || property === name);
                prop.property = name;
                prop.value = value;
                prop.model = model;
                prop.valueOf = metadataValueOf;
                prop.set = function (v) {
                    return function () {
                        linq.model(model).set(name, v);
                    };
                };
                prop.increse = function (v, max) {
                    return function () {
                        var current = parseFloat(model[name]) || 0;
                        var maxValue = parseFloat(max);
                        if (isNaN(maxValue)) maxValue = Number.MAX_VALUE;
                        if (current >= maxValue) return;
                        linq.model(model).set(name, current + (v = parseFloat(v), isNaN(v) ? 1 : v));
                    };
                };

                prop.decrese = function (v, min) {
                    return function () {
                        var current = parseFloat(model[name]) || 0;
                        var maxValue = parseFloat(min);
                        if (isNaN(maxValue)) maxValue = Number.MAX_VALUE;
                        if (current <= maxValue) return;
                        linq.model(model).set(name, current - (v = parseFloat(v), isNaN(v) ? 1 : v));
                    };
                };
            });
        }
        return obj;
    }

    function textNodeFilter() {
        return this.nodeType != 3;
    }

    function arrayTracker(array, action, onChange, context) {
        var length = array.length;
        var changed = action.call(context || array);
        if (changed || length != array.length) onChange.call(context || array);
    }

    function evalObject(value, clone, dataOnly, isInternalEval) {
        var target = value;
        if ($.isArray(value)) {
            if (clone) target = [];
        }
        else if ($.isPlainObject(value)) {
            if (value[metadataProperty] == true) return value.value;
            if (clone) target = {};
        }
        else {
            if (dataOnly && isInternalEval && value instanceof Function) return ignoredValue;
            return value;
        }
        each(value, function (b, v) {
            var metadata = getMetadata(v);
            if (metadata.invalid) {
                var newValue = evalObject(metadata.value, clone, dataOnly, true);
                if (newValue !== ignoredValue && (clone || newValue !== v)) target[b] = newValue;
            }
            else {
                if (dataOnly && metadata.value instanceof Function) return;
                target[b] = metadata.value;
            }
        });
        return target;
    }

    function serialize(value, type, def) {
        var serializer = (typeof type == 'function' ? type : serializers[type]) || def || serializers.def;
        if (serializer) return serializer(value);
        if (value && typeof value.serialize == 'function') return value.serialize();
        return value;
    }

    function deserialize(value, type, def) {
        var deserializer = (typeof type == 'function' ? type : deserializers[type]) || def || deserializers.def;
        if (deserializer) return deserializer(value);
        if (value && typeof value.deserialize == 'function') return value.deserialize();
        return value;
    }

    function getModelData(model, createNew) {
        var d = model[modelProperty];
        if (d == null) {
            if (createNew) return model[modelProperty] = {
                bindings: [],
                containers: [],
                errors: []
            };
            return {};
        }
        return d;
    }

    function evalAjaxResult(data, callback) {
        if (data && data.__isModelResult) {
            switch (data.type) {
                case 'redirect':
                    location.href = data.path;
                    break;
            }
        }
        else if (callback) {
            return callback.call(this, data);
        }
    }

    win.ModelManager = function (model, parent) {
        var me = this;

        this.parent = function () { return parent; };

        this.send = function (options) {
            if (typeof options == 'string') return this.send({ url: options });
            if (options == null) return this;
            if (!options.type) options.type = 'post';
            var data = model;
            if (options.property) {
                data = model[options.property];
            }
            else if (options.eval) {
                data = linq.utils.lambda(options.eval).call(model, model);
            }
            data = evalObject(data, true, true);
            options.data = linq.toJSON(data);
            if (!options.dataType) options.dataType = 'json';
            if (!options.contentType) options.contentType = 'application/json; charset=utf-8';
            options.contentType += '; linq-model-request';
            var oldSuccess = options.success;
            options.success = function (result) {
                evalAjaxResult(result, function () {
                    if (oldSuccess) {
                        oldSuccess.call(this, result);
                    }
                });
            };
            $.ajax(options);
            return this;
        };

        this.parseArguments = function (args, lastIsValue, hasProperty) {
            var obj = model;
            var invalid = obj == null;
            var length = lastIsValue ? args.length - (hasProperty ? 2 : 1) : args.length;
            var metadata;
            if (!invalid) {
                for (var i = 0; i < length; i++) {
                    var p = args[i];
                    metadata = getMetadata(p);
                    if (!metadata.invalid) p = metadata.property;
                    if ($.isArray(p) || $.isPlainObject(p)) {
                        obj = p;
                    }
                    else {
                        obj = obj[p];
                    }
                    if (obj == null) {
                        invalid = true;
                        break;
                    }
                }
            }
            var value = undef;
            if (lastIsValue && !invalid) {
                value = args[args.length - 1];
                metadata = getMetadata(value);
                if (!metadata.invalid) value = metadata.value;
            }
            return {
                invalid: invalid,
                model: obj,
                property: hasProperty ? args[args.length - 2] : undef,
                value: value
            };
        };

        this.error = function (name, data) {
            // overload: error()
            if (arguments.length == 0) {
                return getModelData(model, true).errors;
            }
            // overload: error(clear:Boolean)
            if (name === false) {
                getModelData(model, false).errors = [];
            }
            // overload: error(errors:Array)
            else if ($.isArray(name)) {
                getModelData(model, true).errors.concat(name);
            }
            // overload: error(errors:Object)
            else if ($.isPlainObject(name)) {
                var errors = getModelData(model, true).errors;
                each(name, function (n, v) {
                    errors.push({ key: n, data: v });
                });
            }
            else {
                // overload: error(name:String)
                if (arguments.length == 1) {
                    return linq(getModelData(model, true).errors).where(function (x) {
                        return x && x.key == name;
                    }).select('x.data').array();
                }
                else {
                    // overload: error(name:String, clear:Boolean)
                    if (data === false) {
                        var d = getModelData(model, true);
                        d.errors = linq(d.errors).where(function (x) {
                            return x && x.key != name;
                        }).array();
                    }
                    else {
                        // overload: error(name:String, data:Object)
                        getModelData(model, true).errors.push({ key: name, data: data });
                    }

                }
            }
            return this;
        };

        this.get = function () {
            if (arguments.length == 0) return model;
            return this.parseArguments(arguments, false).model;
        };

        this.attach = function () {
            var args = this.parseArguments(arguments, true);

            if (!args.invalid && typeof args.value == 'function') {
                getModelData(args.model, true).bindings.push(args.value);
            }
            return this;
        };

        this.detach = function () {
            var args = this.parseArguments(arguments, true);
            if (!args.invalid && typeof args.value == 'function') {
                var elements = getModelData(args.model).bindings;
                if (elements) {
                    for (var i = 0; i < elements.length; i++) {
                        if (elements[i] === args.value) {
                            elements.splice(i, 1);
                            break;
                        }
                    }
                }
            }
            return this;
        };

        this.change = function (obj, property, value) {
            if (arguments.length == 0 || obj == "*") return this.change(model, "*");
            if (arguments.length < 3) return this.change(model, arguments[0], arguments[1]);
            if (obj instanceof Array || $.isPlainObject(obj)) {
                var elements = getModelData(obj).bindings;
                if (elements) {
                    each(elements, function (n, e) {
                        if (typeof e == 'function') {
                            e.call(obj, property, value);
                            return;
                        }
                        var binder = $(e).data('binder');
                        if (!binder) return;
                        me.bind(obj, e, binder, property, value);
                    });
                    return true;
                }
            }
            return false;
        };

        this.update = function (newValues) {
            if (newValues) {
                each(newValues, function (name, value) {
                    var metadata = getMetadata(value);
                    if (!metadata.invalid) value = metadata.value;
                    model[name] = value;
                });
                me.change(model, '*');
            }
            return this;
        };

        this.set = function () {
            var oldValue;
            if (arguments.length == 1 && $.isPlainObject(arguments[0])) {
                var changeSet = [];
                each(arguments[0], function (name, value) {
                    oldValue = model[name];
                    var metadata = getMetadata(value);
                    if (!metadata.invalid) value = metadata.value;
                    model[name] = value;
                    changeSet.push({ property: name, value: oldValue });
                });
                each(changeSet, function (index, info) {
                    me.change(model, info.property, info.value);
                });
            }
            else {
                var args = this.parseArguments(arguments, true, true);
                if (!args.invalid) {
                    oldValue = args.model[args.property];
                    args.model[args.property] = args.value;
                    this.change(args.model, args.property, oldValue);
                }
            }
            return this;
        };

        this.push = function () {
            var args = this.parseArguments(arguments, true);
            if (!args.invalid && args.model) {
                var array = args.model;
                if ($.isArray(array)) {
                    arrayTracker(array, function () {
                        array.push(args.value);
                    }, function () {
                        me.change(array, 'push', args.value);
                    }, this);
                }
            }
            return this;
        };

        this.unshift = function () {
            var args = this.parseArguments(arguments, true);
            if (!args.invalid && args.model) {
                var array = args.model;
                if ($.isArray(array)) {
                    arrayTracker(array, function () {
                        array.unshift(args.value);
                    }, function () {
                        me.change(array, 'unshift', args.value);
                    }, this);
                }
            }
            return this;
        };

        this.call = function (method) {
            var args = Array.prototype.slice.call(arguments, 1);
            if (typeof method != 'function') method = model[method];
            if (method) return method.apply(model, args);
            return undef;
        };

        this.apply = function (method, args) {
            if (typeof method != 'function') method = model[method];
            if (method) return method.apply(model, args);
            return undef;
        };

        this.pop = function () {
            var args = this.parseArguments(arguments, false);
            if (!args.invalid && args.model) {
                var array = args.model;
                if ($.isArray(array)) {
                    var oldValue;
                    arrayTracker(array, function () {
                        oldValue = array.pop(args.value);
                    }, function () {
                        me.change(array, 'pop', oldValue);
                    }, this);
                }
            }
            return this;
        };

        this.shift = function () {
            var args = this.parseArguments(arguments, false);
            if (!args.invalid && args.model) {
                var array = args.model;
                if ($.isArray(array)) {
                    var oldValue;
                    arrayTracker(array, function () {
                        oldValue = array.shift(args.value);
                    }, function () {
                        me.change(array, 'shift', oldValue);
                    }, this);
                }
            }
            return this;
        };

        this.remove = function () {
            var args = this.parseArguments(arguments, true);
            if (!args.invalid && args.model) {
                var array = args.model;
                if ($.isArray(array)) {
                    var index = parseInt(args.value, 10) || 0;
                    if (index >= 0 && index < array.length) {
                        var old = array[index];
                        arrayTracker(array, function () {
                            array.splice(index, 1);
                        }, function () {
                            me.change(array, 'remove', { index: index, value: old });
                        }, this);
                    }
                }
            }
            return this;
        };

        this.kill = function () {
            var args = this.parseArguments(arguments);
            if (!args.invalid && ($.isArray(args.model) || $.isPlainObject(args.model))) {
                var containers = getModelData(args.model).containers;
                if (containers) {
                    each(containers, function () {
                        $(this).remove();
                    });
                    containers.splice(0, containers.length);
                }
                delete args.model[modelProperty];
            }
        };

        this.refresh = function () {
            var args = this.parseArguments(arguments, false);
            this.change(args.value, '*', undef);
            return this;
        };

        this.free = function (unbindAllEvents) {
            if ($.isPlainObject(model) || $.isArray(model)) {
                var containers = getModelData(model).containers;
                if (containers && containers.length) {
                    each(containers, function () {
                        if (unbindAllEvents) $('[data-bind]', this).unbind();

                    });

                    containers.splice(0, containers.length);
                }
                delete model[modelProperty];
                each(model, function (name, value) {
                    new ModelManager(value).free();
                });
            }
            return this;
        },

        this.bind = function (obj, element, binder, property, value, metadata, parentManager) {
            if ($(element).data('binding')) return;
            $(element).data('binding', true);
            if (!metadata) metadata = createModelMetadata(obj, property);
            if (!parentManager && parent != null) parentManager = linq.model(parent);
            var bindingArgs = [metadata, obj, me, parentManager];
            var bindingResult = binder.apply(element, bindingArgs);
            var binded = $(element).data('binded');
            if (bindingResult && bindingResult.bindable !== false) {
                var cancel = false;
                if (typeof bindingResult.prepare == 'function') {
                    cancel = bindingResult.prepare.apply(element, bindingArgs) === false;
                }
                if (!cancel) {
                    var before = bindingResult.before;
                    var after = bindingResult.after;
                    delete bindingResult.before;
                    delete bindingResult.after;
                    each(bindingResult, function (type, args) {
                        var provider = bindingProviders[type];
                        if (provider) {
                            cancel = false;
                            var context = {
                                propertyChanged: function (p) {
                                    return property == '*' || p == '*' || p == property;
                                },
                                eval: evalObject,
                                binded: binded,
                                element: element,
                                property: property,
                                model: obj,
                                value: value,
                                params: args == null ? {} : args,
                                args: args || []
                            };
                            if (before) cancel = before.call(element, context, provider) === false;
                            if (!cancel) {
                                provider.call(element, context);
                                if (after) after.call(element, context, provider);
                            }
                        }
                    });
                }
                if (typeof bindingResult.done == 'function') {
                    bindingResult.done.apply(element, bindingArgs);
                }
            }

            this.select = function (selector) {
                selector = linq.utils.lambda(selector);
                return selector ? selector.call(me, model) : model;
            };

            $(element).data('binded', true);
            $(element).data('binding', false);
            return this;
        };

        this.exportData = function (isInclude) {
            if (typeof isInclude != 'boolean') {
                var args = Array.prototype.slice.call(arguments, 1);
                args.unshift(false);
                return me.exportData.apply(me, args);
            }
            var i;
            if (isInclude) {
                var temp = $.isArray(model) ? [] : {};
                for (i = 1; i < arguments.length; i++) {
                    var metaInclude = getMetadata(arguments[i]);
                    var prop = metaInclude.property || metaInclude.value;
                    temp[prop] = model[prop];
                }
                return temp;
            }
            else {
                var newObject = evalObject(model, true, true);
                for (i = 1; i < arguments.length; i++) {
                    var metaExclude = getMetadata(arguments[i]);
                    delete newObject[metaExclude.property || metaExclude.value];
                }
                return newObject;
            }
        };

        this.to = function (container) {
            var obj = this.get();
            if (obj && container) {
                container = $(container).filter(textNodeFilter);
                if (container.length) {
                    container.attr('data-model', true);
                    var data = getModelData(obj, true);
                    var containers = data.containers;
                    container.each(function () {
                        containers.push(this);
                    });
                    var array = container.find('.ui-inline-template').toArray();
                    if (container.hasClass('ui-inline-template')) array.unshift(container);
                    for (var i = array.length - 1; i >= 0; i--) {
                        var element = $(array[i]);
                        var s = $('<script type="text/html"></script>').text(element.html());
                        s.attr('bindable', element.attr('bindable'));
                        while (true) {
                            var id = 'template-' + (new Date().getTime() * Math.random()).toFixed(0);
                            if (!templateIds[id]) {
                                templateIds[id] = true;
                                s.attr('id', id);
                                element.attr('inline-template-id', id);
                                element.removeClass('ui-inline-template');
                                break;
                            }
                        }
                        element.empty();
                        $(document.body).prepend(s);
                    }
                    var bindingElements = container.find('[data-bind]').toArray();
                    if (container.is('[data-bind]')) {
                        bindingElements.push(container);
                    }
                    if (bindingElements.length) {
                        bindingElements = $(bindingElements);
                        var metadata = createModelMetadata(obj, '*');
                        var parentManager = parent != null ? linq.model(parent) : null;

                        var elements = data.bindings;
                        bindingElements.each(function () {
                            var binder = new Function(bindingArgNames, 'with(metadata){return ({' + $(this).attr('data-bind') + '});}');
                            $(this).data('binder', binder);
                            me.bind(obj, this, binder, "*", null, metadata, parentManager);
                            elements.push(this);
                        });
                    }
                }
            }
            return this;
        };

        // wrap method
        each(this, function (property, value) {
            if (value instanceof Function) {
                var wrappedMethod = function () {
                    return value.apply(me, arguments);
                };
                wrappedMethod[managerMethod] = true;
                wrappedMethod.late = function () {
                    var args = arguments;
                    return function () {
                        return wrappedMethod.apply(me, args);
                    };
                };
                me[property] = wrappedMethod;
            }
        });
    };

    linq.model = win.$model = function (model, parent, callback) {
        if (typeof parent == 'function') {
            callback = parent;
            parent = null;
        }
        var manager = new ModelManager(model, parent);
        if (callback) {
            return callback.call(manager, createModelMetadata(model));
        }
        return manager;
    };

    $.extend(linq.model, {
        defaults: defaults
    });

    function createInlineTemplate(element) {
        var templateId = $(element).attr('inline-template-id');
        if (templateId) {
            return $('#' + templateId);
        }
        return null;
    }

    function findInlineTemplate(element) {
        var template = $(element).find('script[type="text/html"]').get(0);
        if (template) {
            $(document.body).append(template);
            return $(template);
        }
        return null;
    }

    bindingProviders.each = function (e) {
        var template = $(e.element).data('each-template');
        if (!template) {
            template = e.params.template;
            if (!template) {
                template = createInlineTemplate(e.element) || findInlineTemplate(e.element) || $([]);
            }
            else {
                template = $(template);
            }
            $(e.element).data('each-template', template);
        }
        if (!template.length) return;

        var bindable = e.params.bindable === false ? false : template.attr('bindable') == 'false' ? false : true;
        var from = getMetadata(e.params.from);
        var to = getMetadata(e.params.to);

        if (typeof from.value != 'undefined' || typeof to.value != 'undefined') {
            if (!e.binded || e.propertyChanged(from.property) || e.propertyChanged(to.property) || (from.invalid && to.invalid)) {
                $(e.element).empty();
                from = parseInt(from.value, 10) || 0;
                to = parseInt(to.value, 10) || 0;
                if (from < to) {
                    for (var i = from; i < to; i++) {
                        var m = { from: from, to: to, value: i };
                        var n = template.tmpl(m).filter(textNodeFilter);
                        n.appendTo(e.element);
                        if (bindable) linq.model(m).to(n);
                    }
                }
            }
            return;
        }

        var metadata = getMetadata(e.params.property);
        var array = metadata.value;
        if (array == null) return;
        if (!$.isArray(array)) array = [array];
        var items = $(e.element).data('items');
        if (!items) $(e.element).data('items', items = []);
        if (!e.binded && !metadata.invalid) {
            // attach changed event
            linq.model(array).attach(function (property, value) {
                var item;
                switch (property) {
                    case 'push':
                        item = template.tmpl(value).filter(textNodeFilter);
                        item.appendTo(e.element);
                        items.push(item);
                        if (bindable) linq.model(value, array).to(item);
                        break;
                    case 'unshift':
                        item = template.tmpl(value).filter(textNodeFilter);
                        item.prependTo(e.element);
                        items.unshift(item);
                        if (bindable) linq.model(value, array).to(item);
                        break;
                    case 'pop':
                        item = items.pop();
                        if (item) item.remove();
                        break;
                    case 'shift':
                        item = items.shift();
                        if (item) item.remove();
                        break;
                    case 'remove':
                        item = items[value.index];
                        if (item) {
                            item.remove();
                            items.splice(value, 1);
                        }
                        break;
                    case 'clear':
                        items.splice(0, items.length);
                        $(e.element).empty();
                        break;
                    default:
                        var index = parseInt(property, 10);
                        if (!isNaN(index)) {
                            item = items[index];
                            if (item) {
                                var previous = item.prev();
                                item.remove();
                                item = template.tmpl(array[index]).filter(textNodeFilter);
                                if (previous.length) {
                                    item.after(previous);
                                }
                                else {
                                    item.prependTo(e.element);
                                }
                            }
                        }
                        break;
                }
            });
        }

        if (!e.binded || metadata.invalid || e.propertyChanged(metadata.property)) {
            $(e.element).empty();
            each(array, function (index, value) {
                var item = template.tmpl(value).filter(textNodeFilter);
                item.appendTo(e.element);
                items.push(item);
                if (bindable) linq.model(value, array).to(item);
            });
        }
    };

    bindingProviders.text = function (e) {
        var metadata = getMetadata(e.params, e.params.property);
        if (e.propertyChanged(metadata.property) || $(e.element).data('last-text') !== metadata.value) {
            $(e.element).data('last-text', metadata.value);
            $(e.element).text(metadata.value);
        }
    };

    bindingProviders.html = function (e) {
        var metadata = getMetadata(e.params, e.params.property);
        if (e.propertyChanged(metadata.property) || $(e.element).data('last-html') !== metadata.value) {
            $(e.element).data('last-html', metadata.value);
            $(e.element).html(metadata.value);
        }
    };

    function bindModelEvent(event, e) {
        if (e.binded) return;
        var callback = linq.utils.lambda(e.params.callback || e.params);
        var args = e.params.args;
        if (typeof callback == 'function') {
            $(e.element).bind(event, function (ee) {
                if (args == null) {
                    return callback.call(this, ee, e.model);
                }
                else {
                    return callback.apply(this, args);
                }
            });
        }
    };

    each(['click', 'mousedown', 'mouseup', 'keyup', 'keydown', 'keypress', 'mouseover', 'focus', 'blur'], function (index, event) {
        bindingProviders[event] = function (e) {
            return bindModelEvent(event, e);
        };
    });

    bindingProviders.visible = function (e) {
        $(e.element).css('visibility', e.eval(e.params) ? 'visible' : 'hidden');
    };

    bindingProviders.display = function (e) {
        $(e.element)[e.eval(e.params) ? 'show' : 'hide']();
    };

    bindingProviders.enable = function (e) {
        $(e.element).attr('disabled', !e.eval(e.params));
    };

    bindingProviders.disable = function (e) {
        $(e.element).attr('disabled', !!e.eval(e.params));
    };

    bindingProviders.checked = function (e) {
        $(e.element).attr('checked', !!e.eval(e.params));
    };

    function onOptgroupChanged(optgroup, array, property, value, textSelector, valueSelector, selectedSelector) {
        var index;
        switch (property) {
            case 'push':
                $(optgroup).append(createOption(value, textSelector, valueSelector, selectedSelector));
                break;
            case 'unshift':
                $(optgroup).prepend(createOption(value, textSelector, valueSelector, selectedSelector));
                break;
            case 'pop':
                $('option:last', optgroup).remove();
                break;
            case 'shift':
                $('option:first', optgroup).remove();
                break;
            case 'remove':
                index = parseInt(value.index, 10);
                if (!isNaN(index)) {
                    $('option:nth-child(' + (index + 1) + ')', optgroup).remove();
                }
                break;
            case 'clear':
                $(optgroup).empty();
                break;
            default:
                index = parseInt(property, 10);
                if (!isNaN(index) && index >= 0 && index < array.length) {
                    value = array(index);
                    var replaceOption = $('option:nth-child(' + (index + 1) + ')', optgroup);
                    createOption(value, textSelector, valueSelector, selectedSelector).after(replaceOption);
                    replaceOption.remove();
                }
                break;
        }
    }

    //    function createArrayHandler(array, handlers, context) {
    //        if (!context) context = array;
    //        linq.model(array).attach(function (property, value) {
    //            switch (property) {
    //                case 'clear': if (handlers.clear) handlers.clear.call(context, value); break;
    //                case 'push': if (handlers.push) handlers.push.call(context, value); break;
    //                case 'pop': if (handlers.pop) handlers.pop.call(context, value); break;
    //                case 'shift': if (handlers.shift) handlers.shift.call(context, value); break;
    //                case 'unshift': if (handlers.unshift) handlers.unshift.call(context, value); break;
    //                case 'remove': if (handlers.remove) handlers.remove.call(context, value.index, value.value); break;
    //                default: if (handlers.set) handlers.set.apply(context, arguments); break;
    //            }
    //        });
    //    }

    function createOption(value, textSelector, valueSelector, selectedSelector) {
        var option = $('<option/>')
            .data('value', value)
            .attr({ selected: selectedSelector(value), value: valueSelector(value) })
            .text(textSelector(value));
        if ($.isPlainObject(value) || $.isArray(value)) {
            // handle object changed
            linq.model(value).attach(function () {
                option
                    .attr({ selected: selectedSelector(value), value: valueSelector(value) })
                    .text(textSelector(value));
            });
        }
        return option;
    }

    function setOptionSelected(container, value, selected) {
        $('option:not([option-caption])', container).each(function () {
            var v = $(this).data('value');
            if (v == value) {
                $(this).attr('selected', selected);
                return false;
            }
        });
    }

    function jsonDateFilter(data, type) {
        if (type != 'json') return data;
        var result = data ? eval('(' + data.replace(/"\\\/(Date\(\d+\))\\\/"/g, 'new $1') + ')') : null;
        return result;
    }

    function jsonConveter(data) {
        var result = data ? eval('(' + data.replace(/"\\\/(Date\(\d+\))\\\/"/g, 'new $1') + ')') : null;
        return result;
    }

    bindingProviders.options = function (e) {
        var metadata = getMetadata(e.params.property, e.params);
        var array = metadata.value;
        if (!$.isArray(array)) return;
        var textSelector = linq.delegates.returnFirst;
        var labelSelector = function (v) { return v == null ? '' : v.label; };
        var valueSelector = linq.delegates.returnFirst;
        var selectedSelector = linq.delegates.returnFalse;
        var selectedArray = null;
        var caption = null;
        var def = '';
        var childrenSelector = linq.delegates.returnNothing;
        if (array !== e.params) {
            textSelector = linq.utils.lambda(e.params.text) || textSelector;
            valueSelector = linq.utils.lambda(e.params.value) || valueSelector;
            var selectedMetadata = getMetadata(e.params.selected);
            if ($.isArray(selectedMetadata.value)) {
                selectedArray = selectedMetadata.value;
                selectedSelector = function (v) {
                    return linq(selectedArray).contains(v);
                };
            }
            else {
                selectedSelector = linq.utils.lambda(selectedMetadata.value) || selectedSelector;
            }

            childrenSelector = linq.utils.lambda(e.params.children) || childrenSelector;
            labelSelector = linq.utils.lambda(e.params.label) || labelSelector;
            caption = e.params.caption;
            def = e.params.def;
        }
        if (!e.binded) {
            var selectionChanging = false;
            if (selectedArray) {
                // handle element changed
                $(e.element).change(function () {
                    selectionChanging = true;
                    var selectedValues = [];
                    $(':selected:not([option-caption])', e.element).each(function () {
                        var value = $(this).data('value');
                        selectedValues.push(value);
                    });

                    var selectedArrayModelManager = linq.model(selectedArray);

                    var temp = [].concat(selectedArray);
                    // remove unselected values
                    for (var i = temp.length - 1; i >= 0; i--) {
                        if (!linq(selectedValues).contains(temp[i])) {
                            selectedArrayModelManager.remove(i);
                            temp.splice(i, 1);
                        }
                    }

                    // add selected values
                    for (i = 0; i < selectedValues.length; i++) {
                        var v = selectedValues[i];
                        if (!temp.length || !linq(temp).contains(v)) {
                            selectedArrayModelManager.push(v);
                        }
                    }

                    selectionChanging = false;
                });

                // handle selectedArray changed
                linq.model(selectedArray).attach(function (property, value) {
                    if (selectionChanging) return;
                    switch (property) {
                        case 'push':
                        case 'unshift':
                            setOptionSelected(e.element, value, true);
                            break;
                        case 'pop':
                        case 'shift':
                            setOptionSelected(e.element, value, false);
                            break;
                        case 'clear':
                            $(e.element).attr('selectedIndex', -1);
                            break;
                        case 'remove':
                            setOptionSelected(e.element, value.value, false);
                            break;
                        default:
                            var index = parseInt(property, 10);
                            if (index >= 0 && index < selectedArray.length) {
                                setOptionSelected(e.element, value, false);
                                setOptionSelected(e.element, selectedArray[index], true);
                            }
                            break;
                    }
                });
            }

            // handle array changed
            linq.model(array).attach(function (property, value) {
                var item, children, optgroup;
                switch (property) {
                    case 'push':
                        children = childrenSelector(value);
                        if (children) {
                            optgroup = $('<optgroup/>').attr('label', labelSelector(value));
                            linq.model(children).attach(function (p, v) {
                                return onOptgroupChanged(optgroup, this.model(), p, v, textSelector, valueSelector, selectedSelector);
                            });
                            each(children, function (i, v) {
                                $(optgroup).append(createOption(v, textSelector, valueSelector, selectedSelector));
                            });
                            $(e.element).append(optgroup);
                        }
                        else {
                            $(e.element).append(createOption(value, textSelector, valueSelector, selectedSelector));
                        }
                        break;
                    case 'unshift':
                        children = childrenSelector(value);
                        if (children) {
                            optgroup = $('<optgroup/>').attr('label', labelSelector(value));
                            linq.model(children).attach(function (p, v) {
                                return onOptgroupChanged(optgroup, this.model(), p, v);
                            });
                            each(children, function (i, v) {
                                $(optgroup).append(createOption(v, textSelector, valueSelector, selectedSelector));
                            });
                            $(e.element).prepend(optgroup);
                        }
                        else {
                            $(e.element).prepend(createOption(value, textSelector, valueSelector, selectedSelector));
                        }
                        break;
                    case 'pop':
                        $(e.element).children('option:not([option-caption]):last').remove();
                        break;
                    case 'shift':
                        $(e.element).children('option:not([option-caption]):first').remove();
                        break;
                    case 'remove':
                        item = $(e.element).children(':not([option-caption])').get(value.index);
                        if (item) $(item).remove();
                        break;
                    case 'clear':
                        $(e.element).children(':not([option-caption])').remove();
                        break;
                    default:
                        var index = parseInt(property, 10);
                        if (!isNaN(index)) {
                            var replaceOption = $(e.element).children(':not([option-caption])').get(index);
                            if (replaceOption.length) {
                                var selectedIndex = $(e.element).attr('selectedIndex');
                                var newItemValue = array[index];
                                children = childrenSelector(newItemValue);
                                if (children) {
                                    optgroup = $('<optgroup/>').attr('label', labelSelector(newItemValue));
                                    linq.model(children).attach(function (p, v) {
                                        return onOptgroupChanged(optgroup, this.model(), p, v, textSelector, valueSelector, selectedSelector);
                                    });
                                    each(children, function (i, v) {
                                        $(optgroup).append(createOption(v, textSelector, valueSelector, selectedSelector));
                                    });
                                    optgroup.after(replaceOption);
                                }
                                else {
                                    createOption(newItemValue, textSelector, valueSelector, selectedSelector).after(replaceOption);
                                }
                                replaceOption.remove();
                                $(e.element).attr('selectedIndex', selectedIndex);
                            }
                        }
                        break;
                }
            });
        }

        if (!e.binded || e.propertyChanged(metadata.property)) {
            $(e.element).empty();
            // create caption
            if (caption) {
                $(e.element).append($('<option/>').attr({ value: def, 'option-caption': true }));
            }
            each(array, function (index, value) {
                var children = childrenSelector(value);
                if (children) {
                    var optgroup = $('<optgroup/>').attr('label', labelSelector(value));
                    linq.model(children).attach(function (p, v) {
                        return onOptgroupChanged(optgroup, this.model(), p, v, textSelector, valueSelector, selectedSelector);
                    });
                    each(children, function (i, v) {
                        $(optgroup).append(createOption(v, textSelector, valueSelector, selectedSelector));
                    });
                    $(e.element).append(optgroup);
                }
                else {
                    $(e.element).append(createOption(value, textSelector, valueSelector, selectedSelector));
                }
            });
        }
    };

    bindingProviders.ajax = function (e) {
        if ($(e.element).data('ajax-updating')) return;
        var options = typeof e.params == 'string' ? { url: e.params} : e.params;
        var metadata = getMetadata(e.params.active);
        var updateProperty = getMetadata(e.params.update);
        var isText = e.params.isText;
        var append = e.params.append;
        if (!e.binded || metadata.invalid || e.propertyChanged(metadata.property)) {
            var request = $(e.element).data('ajax-request');
            if (request) return;
            var success = options.success;
            var converter = options.converter;
            var complete = options.complete;
            if (typeof options.url == 'function') options.url = options.url.call(e.element, e.model);
            if (typeof options.data == 'function')
                options.data = options.data.call(e.element, e.model);
            else if (typeof options.data == 'string') {

            }
            if (!options.context) options.context = e.element;
            // set default data type as 'json'
            if (!updateProperty.invalid && !options.dataType) {
                options.dataFilter = jsonDateFilter;
                options.dataType = 'json';
            }
            options.complete = function () {
                $(e.element).data('ajax-request', null);
                if (complete) return complete.apply(this, arguments);
            };
            options.success = function (data) {
                if (success && success.apply(this, arguments) === false) return;
                if (converter) data = converter.call(this, data);
                if (updateProperty.invalid) {
                    if (isText) {
                        if (append) {
                            $(e.element).append(document.createTextNode(data == null ? '' : data.toString()));
                        }
                        else {
                            $(e.element).text(data);
                        }
                    }
                    else {
                        if (append) {
                            $(e.element).append(data);
                        }
                        else {
                            $(e.element).html(data);
                        }
                    }
                }
                else {
                    try {
                        $(e.element).data('ajax-updating', true);
                        linq.model(e.model).set(updateProperty.property, data);
                    }
                    finally {
                        $(e.element).data('ajax-updating', false);
                    }

                }
            };
            $(e.element).data('ajax-request', $.ajax(options));
        }
    };

    bindingProviders.attr = function (e) {
        each(e.params, function (name, value) {
            var metadata = getMetadata(value);
            if (!e.binded || !metadata.property || e.propertyChanged(metadata.property)) {
                $(e.element).attr(name, metadata.value);
            }
        });
    };

    bindingProviders.css = function (e) {
        each(e.params, function (name, value) {
            var metadata = getMetadata(value);
            if (!e.binded || !metadata.property || e.propertyChanged(metadata.property)) {
                $(e.element).css(name, metadata.value);
            }
        });
    };

    bindingProviders['class'] = function (e) {
        each(e.params, function (name, value) {
            var metadata = getMetadata(value);
            if (metadata.value) {
                $(e.element).addClass(name);
            }
            else {
                $(e.element).removeClass(name);
            }
        });
    };

    bindingProviders.blink = function (e) {
        var array = $.isArray(e.params) ? e.params : [e.params];
        var changed = false;
        if (e.binded) {
            each(array, function (name, value) {
                var metadata = getMetadata(value);
                if (e.propertyChanged(metadata.property || metadata.value)) {
                    changed = true;
                    return false;
                }
            });
        }
        if (!e.binded || changed) {
            $(e.element).hide().fadeIn('normal');
        }
    };

    bindingProviders.value = function (e) {
        var metadata = getMetadata(e.params, e.params.property);
        if (metadata.invalid) return;

        if (!e.binded) {
            var dataType = String;
            switch ($(e.element).attr('data-type') || typeof metadata.value) {
                case 'number': dataType = Number; break;
                case 'string': dataType = String; break;
                case 'boolean': dataType = Boolean; break;
                default:
                    if (metadata.value instanceof Date) {
                        dataType = Date;
                    }
                    break;
            }
            var events = (e.params.events + ',change').replace(/\s+/g, '').split(',');
            each(events, function (i, eventName) {
                $(e.element).bind(eventName, null, function () {
                    if (!eventName) return;
                    var newValue = $(this).val();
                    newValue = deserialize(newValue, e.params.deserializer, dataType);
                    if (eventName != 'change') $(e.element).data('changing', true);
                    linq.model(e.model).set(metadata.property, newValue);
                    if (eventName != 'change') $(e.element).data('changing', false);
                });
            });
        }

        if (e.propertyChanged(metadata.property) && !$(e.element).data('changing')) {
            $(e.element).val(serialize(e.model[metadata.property], e.params.serializer));
        }
    };

    function createColumnBinder(expression) {
        return new Function('model, action', 'return {' + expression + '};');
    }

    function parseSortExpression(expression) {
        var result = [];
        var isArray = $.isArray(expression);
        if (isArray) {
            $.each(expression, function () {
                if (this.desc == null) return;
                var info = { column: this.column, desc: !!this.desc };
                var test = result['$' + info.column];
                if (test) {
                    test.desc = info.desc;
                    return;
                }
                result.push(info);
                result['$' + info.column] = info;
            });
        }
        else {
            var pairs = (expression || '').toString().split(/\s*,\s*/);
            if (pairs.length > 1 || pairs[0]) {
                $.each(pairs, function () {
                    if (this.length == 0) return;
                    var parts = this.split(/\s+/);
                    var info = { column: parts[0], desc: (parts[1] || '').toString().toLowerCase() == 'desc' };
                    var test = result['$' + info.column];
                    if (test) {
                        test.desc = info.desc;
                        return;
                    }
                    result.push(info);
                    result['$' + info.column] = info;
                });
            }
        }
        return result;
    }

    function createPagerItem(value, pager) {
        value = parseInt(value, 10) || 0;
        var result = {
            valid: pager.count > 0,
            value: value,
            current: value == pager.current,
            first: value == 0,
            last: value == pager.count - 1,
            next: pager.current + 1 == value,
            prev: pager.current - 1 == value,
            script: 'linq.model(jQuery(this).tmplItem().data.model).set("' + pager.metadata.property + '", ' + value + ')'
        };
        var name = 'N/A';
        if (result.valid) {
            if (result.first && pager.current > 0) {
                name = 'First';
            }
            else if (result.last && pager.current < pager.count - 1) {
                name = 'Last';
            }
            else if (result.prev && pager.current > 0) {
                name = 'Previous';
            }
            else if (result.next && pager.current < pager.count - 1) {
                name = 'Next';
            }
            else {
                name = (value + 1).toString();
            }
        }
        result.name = name;
        return result;
    }

    bindingProviders.grid = function (e) {
        var currentPageMetadata = getMetadata(e.params.currentPage);
        var currentPage = 0;
        if (typeof currentPageMetadata.value == 'function') {
            currentPage = currentPage.call(e.element, e.model);
        }
        else {
            currentPage = currentPageMetadata.value;
        }
        currentPage = parseInt(currentPage, 10) || 0;

        var pageSizeMetadata = getMetadata(e.params.pageSize);
        var pageSize = 0;
        if (typeof pageSizeMetadata.value == 'function') {
            pageSize = pageSize.call(e.element, e.model);
        }
        else {
            pageSize = pageSizeMetadata.value;
        }
        pageSize = parseInt(pageSize, 10) || 0;


        var sortByMetadata = getMetadata(e.params.sortBy);

        var manager = new ModelManager(e.model);
        var sortable = !!getMetadata(e.params.sortable).value;
        var sortHandle = getMetadata(e.params.sortHandle);
        if (!e.binded) {
            var headSelector = e.params.headSelector || 'thead';
            var bodySelector = e.params.bodySelector || 'tbody';
            var pagerSelector = e.params.pagerSelector || 'tfoot';
            var pagerTemplate = e.params.pagerTemplate;
            var head = $(headSelector, e.element);
            var body = $(bodySelector, e.element);
            var columnElements = e.params.columnSelector ? $(e.params.columnSelector, head) : $('[data-column]', head);
            var avaiableColumns = [];

            pagerSelector = $(pagerSelector, e.element);
            if (pagerSelector.length) {
                if (pagerTemplate) {
                    pagerTemplate = $(pagerTemplate);
                    if (!pagerTemplate.length) pagerTemplate = null;
                }
                else {
                    pagerTemplate = createInlineTemplate(pagerSelector) || findInlineTemplate(pagerSelector);
                }
            }
            else {
                pagerSelector = null;
                pagerTemplate = null;
            }


            columnElements.each(function () {
                var dataColumn = $(this).attr('data-column');
                if (!dataColumn) return;
                var binder = createColumnBinder(dataColumn);
                var initParams = binder.call(this, e.model, manager);
                avaiableColumns.push({
                    binder: binder,
                    element: this,
                    params: initParams
                });
                if (sortable) {
                    var handle = sortHandle.value ? $(sortHandle.value, this) : $(this);
                    var columnElement = this;
                    handle.bind('click', function (ee) {
                        var sortBy = e.model[sortByMetadata.property];
                        var sortByIsArray = $.isArray(sortBy);
                        var sortInfo = parseSortExpression(sortBy);
                        if (!ee.ctrlKey) {
                            if (sortInfo[0] && sortInfo[0].column == initParams.name) {
                                sortInfo = [sortInfo[0]];
                                sortInfo['$' + sortInfo[0].column] = sortInfo[0];
                            }
                            else {
                                sortInfo = [];
                            }
                        }
                        var sortedColumn = sortInfo['$' + initParams.name];
                        var className = '';
                        if (sortedColumn) {
                            sortedColumn.desc = !sortedColumn.desc;
                            className = sortedColumn.desc ? 'ui-sort-desc' : 'ui-sort-asc';
                        }
                        else {
                            sortInfo.push({ column: initParams.name, desc: false });
                            className = 'ui-sort-asc';
                        }
                        if (sortByIsArray) {
                            sortBy = [].concat(sortInfo);
                        }
                        else {
                            sortBy = linq(sortInfo).select('x.column + (x.desc ? " desc" : " asc")').implode(',');
                        }

                        manager.set(sortByMetadata.property, sortBy);

                        if (!ee.ctrlKey) {
                            linq(avaiableColumns).select('@element').jq().removeClass('ui-sort-desc').removeClass('ui-sort-asc');
                        }
                        if (className) $(columnElement).addClass(className);
                    });
                }
            });
            $(e.element).data('grid', {
                model: e.model,
                body: body,
                head: head,
                columns: avaiableColumns,
                pager: {
                    current: currentPage,
                    size: pageSize,
                    template: pagerTemplate,
                    selector: pagerSelector
                }
            });
        }

        var activated = !e.binded;
        if (!activated) {
            var activeMetadata = getMetadata(e.params.active);
            if (activeMetadata.invalid) {
                if (typeof activeMetadata.value == 'function') {
                    activated = activeMetadata.value.call(e.element, e.model);
                }
                else if ($.isArray(activeMetadata.value)) {
                    each(activeMetadata.value, function (i, v) {
                        var m = getMetadata(v);
                        if (typeof m.value == 'function') {
                            activated = m.value.call(e.element, e.model);
                        }
                        else {
                            activated = !!m.value;
                        }
                        if (activated) return false;
                    });
                }
                else {
                    activated = !!activeMetadata.value;
                }
            }
            else {
                activated = e.propertyChanged(activeMetadata.property);
            }
        }

        var gridParams = $(e.element).data('grid');

        if (!activated) {
            if (currentPageMetadata.invalid) {
                activated = gridParams.pager.current != currentPage;
            }
            else {
                activated = e.propertyChanged(currentPageMetadata.property);
            }
        }
        var isSizeChanged = pageSizeMetadata.property && e.property == pageSizeMetadata.property;
        if (!activated) {
            if (pageSizeMetadata.invalid) {
                activated = gridParams.pager.size != pageSize;
            }
            else {
                activated = e.propertyChanged(pageSizeMetadata.property);
            }
        }

        if (isSizeChanged && currentPage != 0) {
            // reset to page 0
            currentPage = 0;
            linq.model(e.model).set(currentPageMetadata.property, currentPage);
        }

        if (activated) {

            gridParams.pager.current = currentPage;
            gridParams.pager.size = pageSize;

            var updateUrl = e.params.url;
            gridParams.source = e.params.source;
            gridParams.total = 0;

            var dataSourceBinder = function () {
                var rowTemplate = e.params.rowTemplate;
                if (rowTemplate) {
                    rowTemplate = $(rowTemplate);
                    if (rowTemplate.length == 0) rowTemplate = null;
                }
                else {
                    // try find inner template
                    rowTemplate = gridParams.body.data('row-template');
                    if (!rowTemplate) {
                        rowTemplate = createInlineTemplate(gridParams.body) || findInlineTemplate(gridParams.body);
                        if (rowTemplate) gridParams.body.data('row-template', rowTemplate);
                    }
                }

                gridParams.body.empty();

                if (rowTemplate) {
                    var bindable = rowTemplate.attr('bindable') !== "false";
                    var columnInfo = {};
                    $.each(gridParams.columns, function (name, value) {
                        var params = value.binder.call(value.element, e.model, manager);
                        if (!params.name) return;
                        columnInfo[params.name] = params;
                    });
                    var startIndex = currentPage * pageSize;
                    $.each(gridParams.source, function (index, value) {
                        var row = {
                            model: gridParams.model,
                            data: value,
                            globalIndex: startIndex + index,
                            index: index,
                            visible: function (columnName) {
                                var c = columnInfo[columnName];
                                return c && c.visible !== false;
                            }
                        };
                        var rowElements = rowTemplate.tmpl(row).filter(textNodeFilter);
                        rowElements.appendTo(gridParams.body);
                        if (bindable) linq.model(value, gridParams.model).to(rowElements);
                    });
                }

                var pager = gridParams.pager;
                if (pager.template && pager.selector) {
                    var pagerModel = {
                        metadata: currentPageMetadata,
                        model: e.model,
                        grid: gridParams,
                        total: gridParams.total,
                        current: currentPage,
                        first: currentPage == 0,
                        next: function (value) {
                            value = parseInt(value, 10) || 0;
                            return value + pagerModel.current < pagerModel.count;
                        },
                        prev: function (value) {
                            value = parseInt(value, 10) || 0;
                            return pagerModel.current - value >= 0;
                        },
                        jump: function (page) {
                            var returnScript = this !== pagerModel;
                            if (returnScript) {
                                return 'jQuery(this).tmplItem().data.jump("' + page + '")';
                            }
                            switch (page) {
                                case 'next': page = pagerModel.current + 1; break;
                                case 'prev': page = pagerModel.current - 1; break;
                                case 'last': page = pagerModel.count - 1; break;
                                case 'first': page = 0; break;
                                default:
                                    page = parseInt(page, 10) || 0;
                                    break;
                            }
                            if (page < 0 || page >= pagerModel.count) return;
                            linq.model(pagerModel.model).set(pagerModel.metadata.property, page);
                        },
                        allPages: function () {
                            var nextPages = [];
                            var prevPages = [];
                            var customPages = 0;
                            var pages = linq(pagerModel.pages).select(function (x) {
                                var item = createPagerItem(x.value, pagerModel);
                                item.name = (x.value + 1).toString();
                                return item;
                            }).array();
                            for (var i = 0; i < arguments.length; i++) {
                                var p = arguments[i];
                                customPages++;
                                switch (p) {
                                    case 'next':
                                        if (!pagerModel.last) pages.push(createPagerItem(pagerModel.current + 1, pagerModel));
                                        continue;
                                    case 'prev':
                                        if (!pagerModel.first) pages.unshift(createPagerItem(pagerModel.current - 1, pagerModel));
                                        continue;
                                    case 'first': p = -pagerModel.current; break;
                                    case 'last': p = pagerModel.count ? pagerModel.count - pagerModel.current - 1 : 0; break;
                                    default: customPages--; break;
                                }
                                p = parseInt(p, 10) || 0;
                                if (p == 0) continue;
                                if (p < 0)
                                    prevPages.push(-p);
                                else
                                    nextPages.push(p);
                            }
                            prevPages = pagerModel.prevPages.apply(pagerModel, prevPages);
                            //prevPages = linq(prevPages).unique('a.value == b.value').array();
                            nextPages = pagerModel.nextPages.apply(pagerModel, nextPages);
                            //nextPages = linq(nextPages).unique('a.value == b.value').array();
                            var results = prevPages.concat(pages).concat(nextPages);
                            return linq(results).unique('a.name == b.name').array();
                        },
                        nextPages: function () {
                            return linq(linq.utils.toArray(arguments)).where(function (x) {
                                return pagerModel.next(x);
                            }).select(function (x) {
                                return createPagerItem(pagerModel.current + x, pagerModel);
                            }).array();
                        },
                        prevPages: function () {
                            return linq(linq.utils.toArray(arguments)).where(function (x) {
                                return pagerModel.prev(x);
                            }).select(function (x) {
                                return createPagerItem(pagerModel.current - x, pagerModel);
                            }).array();
                        },
                        count: Math.ceil(gridParams.total / pageSize)
                    };
                    pagerModel.valid = pagerModel.count > 0;
                    pagerModel.start = pagerModel.count ? currentPage * pageSize + 1 : 0;
                    pagerModel.end = pagerModel.count ? currentPage * pageSize + gridParams.source.length : 0;
                    pagerModel.last = pagerModel.count == 0 || pagerModel.count - 1 == currentPage;
                    pagerModel.pages = linq(pagerModel.count).skip(Math.min(pagerModel.count - 5, currentPage - 2)).take(5).select(function (x) {
                        return createPagerItem(x, pagerModel);
                    }).array();
                    pager.selector.empty();
                    var pagerElement = pager.template.tmpl(pagerModel);
                    pager.selector.append(pagerElement);
                    if (pager.template.attr('bindable') != 'false') {
                        linq.model(pagerModel.model).to(pagerElement);
                    }
                }

            };
            if (updateUrl) {
                if (typeof updateUrl == 'function') updateUrl = updateUrl.call(e.element, e.model);
                var ajaxOptions = e.params.request;

                if (ajaxOptions) {
                    if (typeof ajaxOptions == 'function') ajaxOptions = ajaxOptions.call(e.element, e.model);
                }
                if (!ajaxOptions) ajaxOptions = {};

                var data = {
                    currentPage: currentPage,
                    pageSize: pageSize,
                    sortBy: [].concat(parseSortExpression(sortByMetadata.value))
                };

                if (e.params.serializer) {
                    var serializer = linq.utils.lambda(e.params.serializer);
                    if (serializer) data = serializer(data);
                }
                $.extend(data, ajaxOptions.data);
                $.extend(ajaxOptions, {
                    data: data,
                    url: updateUrl
                });
                if (!ajaxOptions.dataType) ajaxOptions.dataType = 'json';
                if (ajaxOptions.isJson) {
                    if (!ajaxOptions.contentType) ajaxOptions.contentType = 'application/json; charset=utf-8';
                    ajaxOptions.data = linq.toJSON(ajaxOptions.data);
                    ajaxOptions.type = 'post';
                    ajaxOptions.dataType = 'json';
                    ajaxOptions.converters = { 'text json': jsonConveter };
                }


                ajaxOptions.contentType += '; linq-model-request';
                ajaxOptions.success = function (result) {
                    evalAjaxResult(result, function () {
                        var sourceSelector = null;
                        if (e.params.sourceSelector) {
                            sourceSelector = linq.utils.lambda(e.params.sourceSelector);
                        }
                        gridParams.source = sourceSelector ? sourceSelector(result) : result.items;
                        var totalSelector = null;
                        if (e.params.totalSelector) {
                            totalSelector = linq.utils.lambda(e.params.totalSelector);
                        }
                        gridParams.total = (totalSelector ? totalSelector(result) : result.totalItems) || 0;

                        dataSourceBinder();
                    });
                };

                $.ajax(ajaxOptions);
            }
            else {
                if (typeof gridParams.source == 'function') {
                    gridParams.source = gridParams.source.call(e.element, e.model) || [];
                }
                gridParams.total = gridParams.source.total || gridParams.source.length;
                dataSourceBinder();
            }
        }
    };
})(window, window.linq, window.jQuery);